package com.blog.web.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.blog.common.core.constant.ServiceConst;
import com.blog.common.core.exception.ServiceException;
import com.blog.common.core.utils.PageUtils;
import com.blog.common.core.utils.StringUtils;
import com.blog.common.core.web.page.TableDataInfo;
import com.blog.common.domain.entity.Article;
import com.blog.common.domain.dto.ConditionDto;
import com.blog.common.domain.vo.*;
import com.blog.common.redis.service.RedisService;

import com.blog.common.domain.enums.ArticleEnums;
import com.blog.common.mapper.dao.ArticleMapper;
import com.blog.common.mapper.dao.CategoryMapper;
import com.blog.common.mapper.dao.TagMapper;
import com.blog.web.service.ArticleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpSession;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import static com.blog.common.core.constant.CacheConstants.*;
import static com.blog.common.core.constant.ServiceConst.*;

/**
 * <p>
 * 文章 服务实现类
 * </p>
 *
 * @author Valerie
 * @since 2023-02-18
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {


    private final ArticleMapper articleMapper;


    private final CategoryMapper categoryMapper;

    private final TagMapper tagMapper;

    private final RedisService redisService;

    private final HttpSession session;


    @Override
    public TableDataInfo<ArticleHomeVo> listArticles() {
        Page<ArticleHomeVo> articleHomeVoPage = articleMapper.selectArticleHomeVoList(PageUtils.getPage());
        return new TableDataInfo<>(articleHomeVoPage.getRecords(), articleHomeVoPage.getTotal());
    }

    @Override
    public TableDataInfo<ArticlePreviewVoList> listArchives() {
        return SpringUtil.getBean(this.getClass()).listArticlesByCondition(null);
    }

    @Override
    public TableDataInfo<ArticlePreviewVoList> listArticlesByCondition(ConditionDto condition) {
        log.info(PageUtils.getPageNum() +" " + PageUtils.getPageSize());
        // 查询文章
        List<ArticlePreviewVo> articlePreviewVoPage = articleMapper.listArticlesByCondition(PageUtils.getLimitPageSize(), PageUtils.getPageSize(), condition);
        // 查询总数
        long total = (long) Optional.ofNullable(articleMapper.listArticlesByCondition(null, null, condition)).orElse(new ArrayList<>()).size();

        // 以年为单位划分
        List<ArticlePreviewVoList> articlePreviewVoLists = articlePreviewVoPage
                .stream()
                .collect(Collectors.groupingBy(ArticlePreviewVo::getYear))
                .entrySet()
                .stream()
                .map(item -> ArticlePreviewVoList.builder()
                        .articlePreviewVoList(item.getValue())
                        .year(item.getKey())
                        .build())
                .sorted(Comparator.comparing(ArticlePreviewVoList::getYear).reversed())
                .collect(Collectors.toList());
        return new TableDataInfo<>(articlePreviewVoLists, total);
    }

    @Override
    public ArticleVo getArticleById(Serializable articleId) {
        // 推荐文章
        CompletableFuture<List<ArticleRecommendVo>> articleRecommendList = CompletableFuture.supplyAsync(() -> articleMapper.listRecommendArticles(articleId));
        // 最新文章
        CompletableFuture<List<ArticleRecommendVo>> latestArticles = CompletableFuture.supplyAsync(() -> {
            List<Article> articleList = new LambdaQueryChainWrapper<>(articleMapper)
                    .select(Article::getId, Article::getArticleTitle, Article::getArticleCover, Article::getCreateTime)
                    .eq(Article::getStatus, ArticleEnums.PUBLIC.getStatus())
                    .orderByDesc(Article::getId)
                    .last("limit 5")
                    .list();
            return BeanUtil.copyToList(articleList, ArticleRecommendVo.class);
        });

        //查找文章
        ArticleVo article = articleMapper.getArticleById(articleId);
        // 更新文章浏览量
        updateArticleViewsCount(articleId);

        if(Objects.isNull(article)){
            throw new ServiceException("文章不存在！");
        }

        CompletableFuture<Void> previousAndNextArticles = CompletableFuture.runAsync(() -> {
            // 上下篇文章
            Article lastArticle = new LambdaQueryChainWrapper<>(articleMapper)
                    .select(Article::getId, Article::getArticleTitle, Article::getArticleCover)
                    .eq(Article::getStatus, ArticleEnums.PUBLIC.getStatus())
                    .lt(Article::getId, articleId)
                    .orderByAsc(Article::getId)
                    .last("limit 1")
                    .one();

            // 上下篇文章
            Article nextArticle = new LambdaQueryChainWrapper<>(articleMapper)
                    .select(Article::getId, Article::getArticleTitle, Article::getArticleCover)
                    .eq(Article::getStatus, ArticleEnums.PUBLIC.getStatus())
                    .gt(Article::getId, articleId)
                    .orderByAsc(Article::getId)
                    .last("limit 1")
                    .one();
            article.setLastArticle(Optional.ofNullable(BeanUtil.copyProperties(lastArticle, ArticlePaginationVo.class)).orElse(new ArticlePaginationVo()));
            article.setNextArticle(Optional.ofNullable(BeanUtil.copyProperties(nextArticle, ArticlePaginationVo.class)).orElse(new ArticlePaginationVo()));
        });
        CompletableFuture.allOf(articleRecommendList, latestArticles, previousAndNextArticles).join();

        // 封装点赞量和浏览量
        Double score = redisService.zScore(ARTICLE_VIEWS_COUNT, articleId);
        article.setViewsCount(Optional.ofNullable(score).orElse(0.00).longValue());
        article.setLikeCount((Long) Optional.ofNullable(redisService.hGet(ARTICLE_LIKE_COUNT, articleId.toString())).orElse(0L));

        try {
            article.setNewestArticleList(latestArticles.get());
            article.setRecommendArticleList(articleRecommendList.get());
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }

        return article;
    }

    /**
     * 更新文章浏览量
     *
     * @param articleId 文章id
     */
    public void updateArticleViewsCount(Serializable articleId) {
        // 判断是否第一次访问，增加浏览量
        Set<Long> articleSet = (Set<Long>) Optional.ofNullable(session.getAttribute(ARTICLE_SET)).orElse(new HashSet<>());
        if (!articleSet.contains(Long.parseLong(articleId.toString()))) {
            articleSet.add(Long.parseLong(articleId.toString()));
            session.setAttribute(ARTICLE_SET, articleSet);
            // 浏览量+1
            redisService.zIncrement(ARTICLE_VIEWS_COUNT, articleId, 1D);
        }
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveArticleLike(Serializable articleId) {
        // todo
        // 判断是否点赞
        String articleLikeKey = ARTICLE_USER_LIKE;
        if (redisService.sIsMember(articleLikeKey, articleId)) {
            // 点过赞则删除文章id
            redisService.sRemove(articleLikeKey, articleId);
            // 文章点赞量-1
            redisService.hDecr(ARTICLE_LIKE_COUNT, articleId.toString(), 1L);
        } else {
            // 未点赞则增加文章id
            redisService.sAdd(articleLikeKey, articleId);
            // 文章点赞量+1
            redisService.hIncr(ARTICLE_LIKE_COUNT, articleId.toString(), 1L);
        }
    }

    @Override
    public List<ArticleVo> listArticlesByCreateTime() {
        List<Article> articleList = new LambdaQueryChainWrapper<>(articleMapper)
                .orderByDesc(Article::getCreateTime)
                .eq(Article::getStatus, ArticleEnums.PUBLIC.getStatus())
                .last("limit 5")
                .list();
        List<String> list = articleList.stream().map(Article::getArticleCover).collect(Collectors.toList());
        return BeanUtil.copyToList(articleList, ArticleVo.class);
    }

    @Override
    public List<ArchiveSideVo> listArchiveDate(Serializable pageSize) {
        return articleMapper.listArchiveByDate(Long.parseLong(pageSize.toString()));
    }

    @Override
    public TableDataInfo<ArticlePreviewVoList> listArchiveByDate(Serializable archiveDate) {
        Page<Article> articleList = this.page(PageUtils.getPage(),
                new LambdaQueryWrapper<Article>()
                        .eq(Article::getStatus, ArticleEnums.PUBLIC.getStatus())
                .apply("DATE_FORMAT(create_time, '%Y 年 %m 月') = {0}", archiveDate));
        List<ArticlePreviewVo> previewVoLists = BeanUtil.copyToList(articleList.getRecords(), ArticlePreviewVo.class);
        ArticlePreviewVoList articlePreviewVoList = ArticlePreviewVoList.builder()
                .articlePreviewVoList(previewVoLists)
                .year(archiveDate.toString().substring(0, 4))
                .build();

        return new TableDataInfo<>(List.of(articlePreviewVoList), articleList.getTotal());
    }

    @Override
    public List<ArticleRecommendVo> listArticleByRecommend() {
        List<Article> articleList = new LambdaQueryChainWrapper<>(articleMapper)
                .eq(Article::getStatus, IS_TRUE)
                .eq(Article::getIsRecommend, IS_TRUE)
                .last("limit 5")
                .list();
        return BeanUtil.copyToList(articleList, ArticleRecommendVo.class);
    }

    @Override
    public List<ArchivePreviewVoList> getArticleByYearAndMonth() {
        List<Article> articleList = new LambdaQueryChainWrapper<>(articleMapper)
                .select(Article::getId, Article::getArticleTitle, Article::getCreateTime)
                .eq(Article::getIsDelete, IS_FALSE)
                .orderByDesc(Article::getCreateTime)
                .list();
        List<ArchivePreviewVoList> archivePreviewVoLists = new ArrayList<>(articleList.size());
        // 分组 年-》 月
        Map<String, Map<String, List<Article>>> articleMap = articleList.stream()
                .collect(Collectors.groupingBy(d -> DateUtil.format(d.getCreateTime(), "Y"),
                        Collectors.groupingBy(d -> DateUtil.format(d.getCreateTime(), "M"))));
        for (Map.Entry<String, Map<String, List<Article>>> entry : articleMap.entrySet()) {
            ArchivePreviewVo archivePreviewVo = new ArchivePreviewVo();

            for (Map.Entry<String, List<Article>> listEntry : entry.getValue().entrySet()) {
                archivePreviewVo = ArchivePreviewVo.builder()
                        .articlePreviewVoList(BeanUtil.copyToList(listEntry.getValue(), ArticlePreviewVo.class))
                        .month(listEntry.getKey())
                        .count((long) listEntry.getValue().size())
                        .build();
            }
            ArchivePreviewVoList archivePreviewVoList = ArchivePreviewVoList.builder()
                    .archivePreviewVo(archivePreviewVo)
                    .year(entry.getKey())
                    .build();
            archivePreviewVoLists.add(archivePreviewVoList);
        }
        return archivePreviewVoLists;
    }

    @Override
    public TableDataInfo<ArticleSearchVo> listArticlesBySearch(Serializable keywords, Serializable pageType) {
        List<ArticleSearchVo> articleSearchVoList;
        long total;
        if (IS_FALSE == Integer.parseInt(pageType.toString())){
            articleSearchVoList = articleMapper.listArticlesBySearch(keywords);
            total = articleSearchVoList.size();
        }else {
            Page<ArticleSearchVo> articleSearchVoPage = articleMapper.listArticlesBySearchAndPage(keywords, PageUtils.getPage());
            articleSearchVoList = articleSearchVoPage.getRecords();
            total = articleSearchVoPage.getTotal();
        }
        return new TableDataInfo<>(articleSearchVoList, total);
    }
}
